﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Text.RegularExpressions;

namespace PocoReport
{
    public class ReportPrinter
    {
        public Report Report { get; set; }
        public Graphics Graphics { get; set; }
        public Size PageSize { get; set; }

        private TextRow _currentRow;
        private readonly Regex _wordRegex = new Regex("([^\\s]+)|(\\s+)", RegexOptions.Compiled | RegexOptions.Singleline);
        private readonly Regex _whiteSpaveRegex = new Regex("\\s+", RegexOptions.Compiled | RegexOptions.Singleline);
        private float _top;

        public ReportPrinter()
        {
            
        }

        public ReportPrinter(Report report, Graphics graphics)
        {
            Report = report;
            Graphics = graphics;
        }

        public void Print(float top, float left, float width)
        {
            _top = top;
            PrintSection(left, width, Report.Header);
            PrintSection(left, width, Report.Body);
            PrintSection(left, width, Report.Footer);
            StartNewRow(0);
        }

        public IEnumerable<int> GetPages(float top, float left, float width)
        {
            _top = top;
            PrintSection(left, width, Report.Header);
            yield return 1;
            _top = top;
            PrintSection(left, width, Report.Body);
            yield return 2;
            _top = top;
            PrintSection(left, width, Report.Footer);
            StartNewRow(0);
            yield return 3;
        }

        private void PrintSection(float left, float width, Section section)
        {
            PrintParts(section.Parts, section.Style, left, width);
        }

        private void PrintParts(IEnumerable<IPart> parts, Style parentStyle, float left, float width)
        {
            foreach (IPart part in parts)
            {
                PrintPart(part, parentStyle, left, width);
            }
        }

        private void PrintPart(IPart part, Style parentStyle, float left, float width)
        {
            _currentSpan = null;
            Paragraph paragraph = part as Paragraph;
            if(paragraph != null)
            {
                StartNewRow(left);
                PrintParts(paragraph.Parts, paragraph.Style, left, width);
            }
            else
            {
                List list = part as List;
                if(list != null)
                {
                    PrintParts(list.Parts, list.Style, left, width);
                }
                else
                {
                    Table table = part as Table;
                    if(table != null)
                    {
                        PrintTable(left, width, table);
                    }
                    else
                    {
                        Image image = part as Image;
                        if(image != null)
                        {
                            PrintImage(image, left, width);
                        }
                        else
                        {
                            Content content = part as Content;
                            if(content != null)
                            {
                                PrintContent(left, width, content, parentStyle);
                            }
                            else
                            {
                                Text text = part as Text;
                                if(text != null)
                                {
                                    PrintParts(text.Parts, text.Style, left, width);
                                }
                                else
                                {
                                    Chart chart = part as Chart;
                                    if(chart != null)
                                    {
                                        PrintChart(left, width, chart);
                                    }
                                }
                            }
                        }
                    }
                }

            }
        }

        private void PrintChart(float left, float width, Chart chart)
        {
            chart.Site = new TestSite();
            if (_currentRow.Width + chart.Width > width)
            {
                StartNewRow(left);
            }
            ChartSpan span = new ChartSpan(chart, _currentRow);
            _currentRow.Add(span);
        }

        private void PrintContent(float left, float width, Content content, Style parentStyle)
        {
            if (_currentRow == null)
            {
                StartNewRow(left);
            }
            Font font = TextStyle.GetFont(parentStyle);
            MatchCollection matches = _wordRegex.Matches(content.Value);
            foreach (Match wordMatch in matches)
            {
                string word = _whiteSpaveRegex.Replace(wordMatch.Value, " ");
                AddWord(word, font, width);
            }
        }

        private void PrintImage(Image image, float left, float width)
        {
            if(!string.IsNullOrEmpty(image.FileName))
            {
                System.Drawing.Image bmp = System.Drawing.Image.FromFile(image.FileName);
                if(_currentRow.Width + bmp.Width > width)
                {
                    StartNewRow(left);
                }
                ImageSpan span = new ImageSpan(bmp, _currentRow);
                _currentRow.Add(span);
            }
        }

        private void PrintTable(float left, float width, Table table)
        {
            int columnCount = table.GetColumnCount();
            float columnWidth = width / columnCount;
            PrintTableRows(table.Header, left, columnWidth);
            PrintTableRows(table.Body, left, columnWidth);
            PrintTableRows(table.Footer, left, columnWidth);
        }

        private void PrintTableRows(IEnumerable<Row> rows, float left, float columnWidth)
        {
            float rowHeight = _top;
            foreach (Row row in rows)
            {
                StartNewRow(left);
                float rowTop = _top;
                for (int i = 0; i < row.Cells.Count; i++)
                {
                    Cell cell = row.Cells[i];
                    float cellLeft = left + (columnWidth * i);
                    StartNewRow(cellLeft, rowTop);
                    PrintParts(cell.Parts, cell.Style, cellLeft, columnWidth);
                    rowHeight = Math.Max(rowHeight, _currentRow.Height + _top);
                }
            }
            StartNewRow(left, rowHeight);
        }

        private TextSpan _currentSpan;
        private void AddWord(string word, Font font, float width)
        {
            if (_currentSpan == null)
            {
                _currentSpan = new TextSpan(font, _currentRow);
                _currentRow.Add(_currentSpan);
            }
            if (!_currentSpan.AddWord(word, width, Graphics))
            {                
                StartNewRow(_currentRow.Left);
                _currentSpan = new TextSpan(font, _currentRow);
                _currentSpan.AddFirstWord(word, Graphics);
                _currentRow.Add(_currentSpan);
            }
        }

        private void StartNewRow(float left)
        {
            if(_currentRow != null)
            {
                _currentRow.Print(Graphics);
                _top = _currentRow.Top + _currentRow.Height;
                _currentSpan = null;
            }
            _currentRow = new TextRow(_top, left);
        }

        private void StartNewRow(float left, float top)
        {
            if (_currentRow != null)
            {
                _currentRow.Print(Graphics);
                _top = top;
                _currentSpan = null;
            }
            _currentRow = new TextRow(_top, left);
        }
    }

    public class TextRow
    {
        public float Top { get; set; }
        public float Left { get; set; }
        private readonly IList<SpanBase> _spans = new List<SpanBase>();

        public IList<SpanBase> Spans { get { return _spans; } }
        public float Width
        {
            get
            {
                float width = _spans.Sum(s => s.Size.Width);
                return width;
            }
        }

        public float Height
        {
            get
            {
                if(_spans.Count > 0)
                {
                    return _spans.Max(s => s.Size.Height);
                }
                return 0;
            }
        }

        public TextRow(float top, float left)
        {
            Top = top;
            Left = left;
        }

        public void Add(SpanBase span)
        {
            _spans.Add(span);            
        }

        public void Print(Graphics graphics)
        {
            float left = Left;
            foreach (SpanBase span in Spans)
            {
                //graphics.DrawRectangle(Pens.Gray, left, top, span.Size.Width, span.Size.Height);
                span.Print(graphics, Top, left);
                left += span.Size.Width;
            }
        }
    }

    public abstract class SpanBase
    {
        private readonly TextRow _row;
        public TextRow Row { get { return _row; } }
        public SizeF Size { get; protected set; }

        protected SpanBase(TextRow row)
        {
            _row = row;
        }

        public abstract void Print(Graphics graphics, float top, float left);
    }

    public class TextSpan : SpanBase
    {
        public Font Font { get; set; }
        public string Text { get; set; }

        public TextSpan(Font font, TextRow row): base(row)
        {
            Font = font;
        }

        public bool AddWord(string text, float width, Graphics graphics)
        {
            if (Row.Spans.Count == 1 && string.IsNullOrEmpty(Text))
            {
                AddFirstWord(text, graphics);
                return true;
            }

            SizeF newSize = graphics.MeasureString(text, Font);
            if (Row.Width + newSize.Width < width)
            {
                Text = Text + text;
                Size = graphics.MeasureString(Text, Font);
                return true;
            }
            return false;
        }

        public void AddFirstWord(string word, Graphics graphics)
        {
            if (word != " ")
            {
                Text = word;
                Size = graphics.MeasureString(Text, Font);
            }
        }

        public override void Print(Graphics graphics, float top, float left)
        {
            StringFormat format = new StringFormat {LineAlignment = StringAlignment.Far};
            graphics.DrawString(Text, Font, Brushes.Black, new RectangleF(left, top, Size.Width, Row.Height), format);
        }
    }

    public class ImageSpan: SpanBase
    {
        private readonly System.Drawing.Image _image;

        public ImageSpan(System.Drawing.Image image, TextRow row) : base(row)
        {
            _image = image;
            Size = image.Size;
        }

        public override void Print(Graphics graphics, float top, float left)
        {
            float imageTop = Row.Height - _image.Height;
            graphics.DrawImage(_image, left, top + imageTop, _image.Width, _image.Height);
        }
    }

    public class ChartSpan : SpanBase
    {
        private readonly Chart _chart;

        public ChartSpan(Chart chart, TextRow row) : base(row)
        {
            _chart = chart;
            Size = chart.Size;
        }

        public override void Print(Graphics graphics, float top, float left)
        {
            float imageTop = Row.Height - _chart.Height;
            _chart.Printing.PrintPaint(graphics, new Rectangle((int)left, (int)(top + imageTop), _chart.Width, _chart.Height));
        }
    }

    public class TestSite : ISite
    {
        public object GetService(Type serviceType)
        {
            return null;
        }

        public IComponent Component
        {
            get { throw new NotImplementedException(); }
        }

        public IContainer Container
        {
            get { return null; }
        }

        public bool DesignMode
        {
            get { return true; }
        }

        public string Name
        {
            get { return null; }
            set { throw new NotImplementedException(); }
        }
    }
}
